/*******************************************************************************
 * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 ******************************************************************************/
package org.cloudifysource.restclient;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.SystemDefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.internal.ProcessorTypes;
import org.cloudifysource.dsl.rest.AddTemplatesException;
import org.cloudifysource.dsl.rest.request.AddTemplatesRequest;
import org.cloudifysource.dsl.rest.request.InstallApplicationRequest;
import org.cloudifysource.dsl.rest.request.InstallServiceRequest;
import org.cloudifysource.dsl.rest.request.InvokeCustomCommandRequest;
import org.cloudifysource.dsl.rest.request.SetServiceInstancesRequest;
import org.cloudifysource.dsl.rest.response.AddTemplatesResponse;
import org.cloudifysource.dsl.rest.response.ApplicationDescription;
import org.cloudifysource.dsl.rest.response.DeploymentEvent;
import org.cloudifysource.dsl.rest.response.DeploymentEvents;
import org.cloudifysource.dsl.rest.response.GetMachineDumpFileResponse;
import org.cloudifysource.dsl.rest.response.GetMachinesDumpFileResponse;
import org.cloudifysource.dsl.rest.response.GetPUDumpFileResponse;
import org.cloudifysource.dsl.rest.response.GetTemplateResponse;
import org.cloudifysource.dsl.rest.response.InstallApplicationResponse;
import org.cloudifysource.dsl.rest.response.InstallServiceResponse;
import org.cloudifysource.dsl.rest.response.InvokeInstanceCommandResponse;
import org.cloudifysource.dsl.rest.response.InvokeServiceCommandResponse;
import org.cloudifysource.dsl.rest.response.ListTemplatesResponse;
import org.cloudifysource.dsl.rest.response.Response;
import org.cloudifysource.dsl.rest.response.ServiceDescription;
import org.cloudifysource.dsl.rest.response.ShutdownManagementResponse;
import org.cloudifysource.dsl.rest.response.UninstallApplicationResponse;
import org.cloudifysource.dsl.rest.response.UninstallServiceResponse;
import org.cloudifysource.dsl.rest.response.UploadResponse;
import org.cloudifysource.restclient.exceptions.RestClientException;
import org.cloudifysource.restclient.messages.MessagesUtils;
import org.cloudifysource.restclient.messages.RestClientMessageKeys;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;


/**
 * This class performs all the calls to the REST API, using the {@link RestClientExecutor}.
 * 
 * @author yael
 * 
 */
public class RestClient {

	private static final Logger logger = Logger.getLogger(RestClient.class.getName());

	private static final String FAILED_CREATING_CLIENT = "failed_creating_client";
	private static final String HTTPS = "https";

	private static final String UPLOAD_CONTROLLER_URL = "/upload/";
	private static final String DEPLOYMENT_CONTROLLER_URL = "/deployments/";
	private static final String TEMPLATES_CONTROLLER_URL = "/templates/";
	private static final String SHUTDOWN_MANAGERS_CONTROLLER_URL = "/management/";

	private static final String INSTALL_SERVICE_URL_FORMAT = "%s/services/%s";
	private static final String INSTALL_APPLICATION_URL_FORMAT = "%s";
	private static final String UPLOAD_URL_FORMAT = "%s";
	private static final String GET_DEPLOYMENT_EVENTS_URL_FORMAT = "%s/events/?from=%s&to=%s";
	private static final String GET_SERVICE_DESCRIPTION_URL_FORMAT = "%s/service/%s/description";
	private static final String GET_SERVICES_DESCRIPTION_URL_FORMAT = "%s/description";
	private static final String GET_APPLICATION_DESCRIPTION_URL_FORMAT = "applications/%s/description";
	private static final String GET_APPLICATION_DESCRIPTIONS_URL_FORMAT = "applications/description";
	private static final String ADD_TEMPALTES_URL_FORMAT = "";
	private static final String GET_TEMPALTE_URL_FORMAT = "%s";
	private static final String LIST_TEMPALTES_URL_FORMAT = "";
	private static final String REMOVE_TEMPALTE_URL_FORMAT = "%s";
	private static final String INVOKE_SERVICE_COMMAND_URL_FORMAT = "applications/%s/services/%s/invoke";
	private static final String INVOKE_INSTANCE_COMMAND_URL_FORMAT = "applications/%s/services/%s/instances/%s/invoke";

	private static final String SET_INSTANCES_URL_FORMAT = "%s/services/%s/count";
	private static final String GET_LAST_EVENT_URL_FORMAT = "%s/events/last/";

	private static final String SHUTDOWN_MANAGERS_URL_FORMAT = "controllers";
	private static final String GET_PU_DUMP_FILE_URL_FORMAT = "dump/processing-units/?fileSizeLimit=%s";
	private static final String GET_MACHINE_DUMP_FILE_URL_FORMAT = "dump/machine/%s/?processors=%s&fileSizeLimit=%s";
	private static final String GET_MACHINES_DUMP_FILE_URL_FORMAT = "dump/machines/?processors=%s&fileSizeLimit=%s";

	protected final RestClientExecutor executor;
	private final String versionedDeploymentControllerUrl;
	protected String versionedUploadControllerUrl;
	protected String versionedTemplatesControllerUrl;
	protected String shutdownManagersControllerUrl;

	public RestClient(final URL url,
			final String username,
			final String password,
			final String apiVersion) throws RestClientException {

		versionedDeploymentControllerUrl = apiVersion + DEPLOYMENT_CONTROLLER_URL;
		versionedUploadControllerUrl = apiVersion + UPLOAD_CONTROLLER_URL;
		versionedTemplatesControllerUrl = apiVersion + TEMPLATES_CONTROLLER_URL;
		shutdownManagersControllerUrl = apiVersion + SHUTDOWN_MANAGERS_CONTROLLER_URL;
		this.executor = createExecutor(url, apiVersion);

		setCredentials(username, password);
	}

	/**
	 * 
	 * @throws RestClientException .
	 */
	public void connect() throws RestClientException {
		final String url = versionedDeploymentControllerUrl + "testrest";
		log(Level.FINE, "[connect] - sending GET request to REST [" + url + "]");
		executor.get(url, new TypeReference<Response<Void>>() {
		});
	}

	/**
	 * Sets the credentials.
	 * 
	 * @param username
	 *            .
	 * @param password
	 *            .
	 */
	public void setCredentials(final String username, final String password) {
		executor.setCredentials(username, password);
	}

	/**
	 * Executes a rest api call to install a specific service.
	 * 
	 * @param applicationName
	 *            The name of the application.
	 * @param serviceName
	 *            The name of the service to install.
	 * @param request
	 *            The install service request.
	 * @return The install service response.
	 * @throws RestClientException .
	 */
	public InstallServiceResponse installService(final String applicationName, final String serviceName,
			final InstallServiceRequest request) throws RestClientException {
		String effAppName = applicationName;
		if (applicationName == null) {
			effAppName = CloudifyConstants.DEFAULT_APPLICATION_NAME;
		}
		if (serviceName == null) {
			throw MessagesUtils.createRestClientException(CloudifyErrorMessages.SERVICE_NAME_MISSING.getName());
		}
		if (request == null) {
			throw MessagesUtils.createRestClientException(
					CloudifyErrorMessages.INSTALL_SERVICE_REQUEST_MISSING.getName());
		}
		final String installServiceUrl = getFormattedUrl(
				versionedDeploymentControllerUrl,
				INSTALL_SERVICE_URL_FORMAT,
				effAppName,
				serviceName);

		log(Level.FINE, "[installService] - sending POST request to REST [" + installServiceUrl + "]");
		return executor.postObject(installServiceUrl, request, new TypeReference<Response<InstallServiceResponse>>() {
		});
	}

	/**
	 * Executes a rest api call to install an application.
	 * 
	 * @param applicationName
	 *            The name of the application.
	 * @param request
	 *            The install service request.
	 * @return The install service response.
	 * @throws RestClientException .
	 */
	public InstallApplicationResponse installApplication(final String applicationName,
			final InstallApplicationRequest request) throws RestClientException {
		final String installApplicationUrl = getFormattedUrl(
				versionedDeploymentControllerUrl,
				INSTALL_APPLICATION_URL_FORMAT,
				applicationName);

		log(Level.FINE, "[installApplication] - sending POST request to REST [" + installApplicationUrl + "]");
		return executor.postObject(installApplicationUrl, request,
				new TypeReference<Response<InstallApplicationResponse>>() {
				});
	}

	/**
	 * Uninstalls the specified service.
	 * 
	 * @param applicationName
	 *            The application containing the service.
	 * @param serviceName
	 *            The service name.
	 * @param timeoutInMinutes
	 *            Timeout in minutes.
	 * @return an uninstall service response object.
	 * @throws RestClientException
	 *             Indicates the uninstall operation failed.
	 */
	public UninstallServiceResponse uninstallService(final String applicationName, final String serviceName,
			final int timeoutInMinutes) throws RestClientException {

		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				INSTALL_SERVICE_URL_FORMAT,
				applicationName,
				serviceName);
		final Map<String, String> requestParams = new HashMap<String, String>();
		requestParams.put(CloudifyConstants.REQ_PARAM_TIMEOUT_IN_MINUTES, String.valueOf(timeoutInMinutes));

		log(Level.FINE, "[uninstallService] - sending DELETE request to REST [" + url + "]");
		return executor.delete(url, requestParams, new TypeReference<Response<UninstallServiceResponse>>() {
		});
	}

	/**
	 * Uninstalls the specified application.
	 * 
	 * @param applicationName
	 *            The application name.
	 * @param timeoutInMinutes
	 *            Timeout in minutes.
	 * @return an uninstall application response object.
	 * 
	 * @throws RestClientException
	 *             Indicates the uninstall operation failed.
	 */
	public UninstallApplicationResponse uninstallApplication(final String applicationName, final int timeoutInMinutes)
			throws RestClientException {
		final String url = versionedDeploymentControllerUrl + applicationName;
		final Map<String, String> requestParams = new HashMap<String, String>();
		requestParams.put(CloudifyConstants.REQ_PARAM_TIMEOUT_IN_MINUTES, String.valueOf(timeoutInMinutes));

		log(Level.FINE, "[uninstallApplication] - sending DELETE request to REST [" + url + "]");
		return executor.delete(url, requestParams, new TypeReference<Response<UninstallApplicationResponse>>() {
		});
	}

	/**
	 * Uploads a file to the repository.
	 * 
	 * @param fileName
	 *            The name of the file to upload.
	 * @param file
	 *            The file to upload.
	 * @return upload response.
	 * @throws RestClientException .
	 */
	public UploadResponse upload(final String fileName, final File file) throws RestClientException {
		validateFile(file);
		final String finalFileName = fileName == null ? file.getName() : fileName;
		if (logger.isLoggable(Level.FINE)) {
			logger.fine("[getDeploymentEvents] - uploading file "
					+ file.getAbsolutePath() + " with name " + finalFileName);
		}
		final String uploadUrl = getFormattedUrl(
				versionedUploadControllerUrl,
				UPLOAD_URL_FORMAT,
				finalFileName);

		log(Level.FINE, "[upload] - sending POST request to REST [" + uploadUrl + "]");
		final UploadResponse response =
				executor.postFile(
						uploadUrl,
						file,
						CloudifyConstants.UPLOAD_FILE_PARAM_NAME,
						new TypeReference<Response<UploadResponse>>() {
						});
		return response;
	}

	/**
	 * Provides access to life cycle events of a service.
	 * 
	 * @param deploymentId
	 *            The deployment id given at installation time.
	 * @param from
	 *            The starting event index.
	 * @param to
	 *            The last event index. passing -1 means all events (limit to 100 at a time)
	 * @return The events.
	 * @throws RestClientException .
	 */
	public DeploymentEvents getDeploymentEvents(final String deploymentId, final int from, final int to)
			throws RestClientException {
		validateDeploymentID(deploymentId, "getDeploymentEvents(String,int,int)");
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_DEPLOYMENT_EVENTS_URL_FORMAT,
				deploymentId,
				String.valueOf(from),
				String.valueOf(to));
		log(Level.FINE, "[getDeploymentEvents] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<DeploymentEvents>>() {
		});
	}

	private void validateDeploymentID(final String deploymentId, final String methodName) throws RestClientException {
		if (deploymentId == null) {
			logger.warning("[" + methodName + "] - deployment ID is missing.");
			throw MessagesUtils.createRestClientException(
					CloudifyErrorMessages.MISSING_DEPLOYMENT_ID.getName(), methodName);
		}
	}

	/**
	 * 
	 * @param appName
	 *            .
	 * @param serviceName
	 *            .
	 * @return ServiceDescription.
	 * @throws RestClientException .
	 */
	public ServiceDescription getServiceDescription(final String appName, final String serviceName)
			throws RestClientException {
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_SERVICE_DESCRIPTION_URL_FORMAT,
				appName,
				serviceName);
		log(Level.FINE, "[getServiceDescription] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<ServiceDescription>>() {
		});
	}

	/**
	 * Retrieves a list of services description by deployment id.
	 * 
	 * @param deploymentId
	 *            The deployment id.
	 * @return list of {@link ServiceDescription}
	 * @throws RestClientException
	 */
	public List<ServiceDescription> getServiceDescriptions(final String deploymentId)
			throws RestClientException {
		validateDeploymentID(deploymentId, "getServiceDescriptions(String)");
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_SERVICES_DESCRIPTION_URL_FORMAT,
				deploymentId);
		log(Level.FINE, "[getServiceDescriptions] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<List<ServiceDescription>>>() {
		});
	}

	/**
	 * 
	 * @param appName
	 *            .
	 * @return ApplicationDescription.
	 * @throws RestClientException .
	 */
	public ApplicationDescription getApplicationDescription(final String appName) throws RestClientException {
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_APPLICATION_DESCRIPTION_URL_FORMAT,
				appName);
		log(Level.FINE, "[getApplicationDescription] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<ApplicationDescription>>() {
		});
	}

	/**
	 * 
	 * @return List of ApplicationDescription objects.
	 * @throws RestClientException .
	 */
	public List<ApplicationDescription> getApplicationDescriptionsList() throws RestClientException {
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_APPLICATION_DESCRIPTIONS_URL_FORMAT);
		log(Level.FINE, "[getApplicationDescriptionsList] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<List<ApplicationDescription>>>() {
		});
	}

	/**
	 * @param appName
	 *            The application the services to be listed are part of.
	 * @return List of service descriptions
	 * @throws RestClientException .
	 */
	public List<ServiceDescription> getServicesDescriptionList(final String appName) throws RestClientException {
		return getApplicationDescription(appName).getServicesDescription();

	}

	/********
	 * Manually Scales a specific service in/out.
	 * 
	 * @param applicationName
	 *            the service's application name.
	 * @param serviceName
	 *            the service name.
	 * @param request
	 *            the scale request details.
	 * @throws RestClientException
	 *             in case of an error.
	 */
	public void setServiceInstances(final String applicationName, final String serviceName,
			final SetServiceInstancesRequest request) throws RestClientException {
		if (request == null) {
			throw new IllegalArgumentException("request may not be null");
		}

		final String setInstancesUrl = getFormattedUrl(
				versionedDeploymentControllerUrl,
				SET_INSTANCES_URL_FORMAT,
				applicationName,
				serviceName);
		log(Level.FINE, "[setServiceInstances] - sending POST request to REST [" + setInstancesUrl + "]");
		executor.postObject(
				setInstancesUrl,
				request,
				new TypeReference<Response<Void>>() {
				}
				);

	}

	/**
	 * Invokes the command specified in the request on all the instances of the given service.
	 * 
	 * @param applicationName
	 *            the service's application name.
	 * @param serviceName
	 *            the service's name.
	 * @param request
	 *            the InvokeCustomCommandRequest containing the command and parameters
	 * @return InvokeServiceCommandResponse the response holding the results of the command invocation per service
	 * @throws RestClientException
	 *             when the invocation failed or the service/instance were not found.
	 */
	public InvokeServiceCommandResponse invokeServiceCommand(final String applicationName,
			final String serviceName, final InvokeCustomCommandRequest request)
			throws RestClientException {

		if (request == null) {
			throw new IllegalArgumentException("request may not be null");
		}

		InvokeServiceCommandResponse result = null;
		final String invokeCommandUrl = getFormattedUrl(
				versionedDeploymentControllerUrl,
				INVOKE_SERVICE_COMMAND_URL_FORMAT,
				applicationName,
				serviceName);

		result = executor.postObject(
				invokeCommandUrl,
				request,
				new TypeReference<Response<InvokeServiceCommandResponse>>() {
				}
				);

		return result;
	}

	/**
	 * Invokes the command specified in the request on the specified instance of the given service.
	 * 
	 * @param applicationName
	 *            the service's application name.
	 * @param serviceName
	 *            the service's name.
	 * @param instanceId
	 *            the relevant instance id
	 * @param request
	 *            the InvokeCustomCommandRequest containing the command and parameters
	 * @return InvokeServiceCommandResponse the response holding the results of the command invocation per service
	 * @throws RestClientException
	 *             when the invocation failed or the service/instance were not found.
	 */
	public InvokeInstanceCommandResponse invokeInstanceCommand(final String applicationName,
			final String serviceName, final int instanceId, final InvokeCustomCommandRequest request)
			throws RestClientException {

		if (request == null) {
			throw new IllegalArgumentException("request may not be null");
		}

		final String instanceIdStr = String.valueOf(instanceId);

		InvokeInstanceCommandResponse result = null;
		final String invokeCommandUrl = getFormattedUrl(
				versionedDeploymentControllerUrl,
				INVOKE_INSTANCE_COMMAND_URL_FORMAT,
				applicationName,
				serviceName,
				instanceIdStr);

		result = executor.postObject(
				invokeCommandUrl,
				request,
				new TypeReference<Response<InvokeInstanceCommandResponse>>() {
				}
				);

		return result;
	}

	/********
	 * Retrieves last event indes for this deployment id.
	 * 
	 * @param deploymentId
	 *            The deploymentId.
	 * @return {@link DeploymentEvents}
	 * @throws RestClientException
	 *             in case of an error on the rest server.
	 */
	public DeploymentEvent getLastEvent(final String deploymentId) throws RestClientException {
		validateDeploymentID(deploymentId, "getLastEvent(String)");
		final String url = getFormattedUrl(
				versionedDeploymentControllerUrl,
				GET_LAST_EVENT_URL_FORMAT,
				deploymentId);
		log(Level.FINE, "[getLastEvent] - sending GET request to REST [" + url + "]");
		return executor.get(url, new TypeReference<Response<DeploymentEvent>>() {
		});

	}

	/**
	 * Validate file before uploading.
	 * 
	 * @param file
	 *            The file to upload.
	 * @throws RestClientException .
	 */
	protected void validateFile(final File file) throws RestClientException {
		if (file == null) {
			throw MessagesUtils.createRestClientException(RestClientMessageKeys.UPLOAD_FILE_MISSING.getName());
		}
		final String absolutePath = file.getAbsolutePath();
		if (!file.exists()) {
			throw MessagesUtils.createRestClientException(RestClientMessageKeys.UPLOAD_FILE_DOESNT_EXIST.getName(),
					absolutePath);
		}
		if (!file.isFile()) {
			throw MessagesUtils.createRestClientException(RestClientMessageKeys.UPLOAD_FILE_NOT_FILE.getName(),
					absolutePath);
		}
		final long length = file.length();
		if (length > CloudifyConstants.DEFAULT_UPLOAD_SIZE_LIMIT_BYTES) {
			throw MessagesUtils.createRestClientException(
					RestClientMessageKeys.UPLOAD_FILE_SIZE_LIMIT_EXCEEDED.getName(), absolutePath, length,
					CloudifyConstants.DEFAULT_UPLOAD_SIZE_LIMIT_BYTES);
		}
	}

	private RestClientExecutor createExecutor(final URL url, final String apiVersion) throws RestClientException {
		SystemDefaultHttpClient httpClient;
		if (HTTPS.equals(url.getProtocol())) {
			httpClient = getSSLHttpClient(url);
		} else {
			httpClient = new SystemDefaultHttpClient();
		}
		final HttpParams httpParams = httpClient.getParams();
		
		HttpConnectionParams.setConnectionTimeout(httpParams, CloudifyConstants.DEFAULT_HTTP_CONNECTION_TIMEOUT);
		int socketTimeout = getSocketTimeout();
		logger.fine("setting rest client socket timeout to: " + socketTimeout);
		HttpConnectionParams.setSoTimeout(httpParams, socketTimeout);
		return new RestClientExecutor(httpClient, url);
	}

	/**
	 * Returns a HTTP client configured to use SSL.
	 * 
	 * @param url
	 * 
	 * @return HTTP client configured to use SSL
	 * @throws org.cloudifysource.restclient.exceptions.RestClientException
	 *             Reporting different failures while creating the HTTP client
	 */
	private SystemDefaultHttpClient getSSLHttpClient(final URL url) throws RestClientException {
		try {
			final X509TrustManager trustManager = createTrustManager();
			final SSLContext ctx = SSLContext.getInstance("TLS");
			ctx.init(null, new TrustManager[] { trustManager }, null);
			final SSLSocketFactory ssf = new SSLSocketFactory(ctx, createHostnameVerifier());
			final AbstractHttpClient base = new SystemDefaultHttpClient();
			SystemDefaultHttpClient httpClient = new SystemDefaultHttpClient(base.getParams());
			httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme(HTTPS, url.getPort(), ssf));
			return httpClient;
		} catch (final Exception e) {
			throw new RestClientException(FAILED_CREATING_CLIENT, "Failed creating http client",
					ExceptionUtils.getFullStackTrace(e));
		}
	}

	private X509TrustManager createTrustManager() {
		final X509TrustManager tm = new X509TrustManager() {

			@Override
			public X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			@Override
			public void checkServerTrusted(final X509Certificate[] chain, final String authType)
					throws CertificateException {
			}

			@Override
			public void checkClientTrusted(final X509Certificate[] chain, final String authType)
					throws CertificateException {
			}
		};
		return tm;
	}

	private X509HostnameVerifier createHostnameVerifier() {
		final X509HostnameVerifier verifier = new X509HostnameVerifier() {

			@Override
			public boolean verify(final String arg0, final SSLSession arg1) {
				return true;
			}

			@Override
			public void verify(final String host, final String[] cns, final String[] subjectAlts)
					throws SSLException {
			}

			@Override
			public void verify(final String host, final X509Certificate cert)
					throws SSLException {
			}

			@Override
			public void verify(final String host, final SSLSocket ssl)
					throws IOException {
			}
		};
		return verifier;
	}

	/**
	 * 
	 * @param controllerUrl
	 *            .
	 * @param format
	 *            .
	 * @param args
	 *            .
	 * @return the formatted URL.
	 */
	protected String getFormattedUrl(final String controllerUrl, final String format, final String... args) {
		return controllerUrl + String.format(format, (Object[]) args);
	}

	/**
	 * Executes a rest API call to get template.
	 * 
	 * @param templateName
	 *            the name of the template to get.
	 * @return GetTemplateResponse.
	 * @throws RestClientException .
	 */
	public GetTemplateResponse getTemplate(final String templateName)
			throws RestClientException {
		final String getTempalteInternalUrl = getFormattedUrl(
				versionedTemplatesControllerUrl,
				GET_TEMPALTE_URL_FORMAT,
				templateName);
		log(Level.FINE, "[getTemplate] - sending GET request to REST ["
				+ getTempalteInternalUrl + "]");
		return executor.get(getTempalteInternalUrl,
				new TypeReference<Response<GetTemplateResponse>>() {
				});
	}

	/**
	 * Executes a rest API call to list templates.
	 * 
	 * @return ListTemplatesResponse.
	 * @throws RestClientException .
	 */
	public ListTemplatesResponse listTemplates() throws RestClientException {
		final String listTempaltesInternalUrl = getFormattedUrl(
				versionedTemplatesControllerUrl,
				LIST_TEMPALTES_URL_FORMAT);
		log(Level.FINE, "[listTemplates] - sending GET request to REST ["
				+ listTempaltesInternalUrl + "]");
		return executor.get(listTempaltesInternalUrl,
				new TypeReference<Response<ListTemplatesResponse>>() {
				});
	}

	/**
	 * Executes a rest API call to add templates to all REST instances.
	 * 
	 * @param request
	 *            contains the templates folder.
	 * @return AddTemplatesResponse.
	 * @throws AddTemplatesException
	 *             If failed to add all templates (includes partial failure).
	 * @throws RestClientException .
	 */
	public AddTemplatesResponse addTemplates(final AddTemplatesRequest request)
			throws RestClientException, AddTemplatesException {
		final String addTempaltesUrl = getFormattedUrl(
				versionedTemplatesControllerUrl,
				ADD_TEMPALTES_URL_FORMAT);
		AddTemplatesResponse response = null;
		try {
			log(Level.FINE, "[addTemplates] - sending POST request to REST ["
					+ addTempaltesUrl + "]");
			return executor.postObject(
					addTempaltesUrl,
					request,
					new TypeReference<Response<AddTemplatesResponse>>() {
					});
		} catch (final RestClientException e) {
			final String verbose = e.getVerbose();
			// may be partial failure - in this case the verbose contains the AddTemplatesResponse object.
			log(Level.FINE, "[addTemplates] - caught RestClientException, "
					+ "trying to read the response from the verbose[ " + verbose + "]");
			try {
				response = new ObjectMapper().readValue(verbose, AddTemplatesResponse.class);
				throw new AddTemplatesException(response);
			} catch (final JsonProcessingException e1) {
				// failed to read the response from the verbose => not a partial failure
				// => throwing the original exception
				log(Level.FINE, "[addTemplates] - caught JsonProcessingException "
						+ "when tried to read the response from the verbose, "
						+ " throwing the RestClientException that constructed "
						+ "from the original exception");
				throw e;
			} catch (final IOException e1) {
				// failed to read the response from the verbose => not a partial failure
				// => throwing the original exception
				if (logger.isLoggable(Level.FINE)) {
					logger.log(Level.FINE,
							"[addTemplates] - caught IOException "
									+ "when tried to read the response from the verbose, "
									+ " throwing the RestClientException that constructed "
									+ "from the original exception", e1);
				}
				throw e;
			}
		}
	}

	/**
	 * Executes a rest API call to remove template from all REST instances.
	 * 
	 * @param templateName
	 *            the template's name to remove.
	 * @throws RestClientException .
	 */
	public void removeTemplate(final String templateName)
			throws RestClientException {
		final String removeTempalteUrl = getFormattedUrl(
				versionedTemplatesControllerUrl,
				REMOVE_TEMPALTE_URL_FORMAT,
				templateName);
		log(Level.FINE, "[removeTemplate] - sending DELETE request to REST ["
				+ removeTempalteUrl + "]");
		executor.delete(
				removeTempalteUrl,
				new TypeReference<Response<Void>>() {
				});
	}

	/**
	 * Executes a rest API call to shutdown the managers of the current cloud.
	 * 
	 * @return ShutdownManagementResponse
	 * @throws RestClientException 
	 */
	public ShutdownManagementResponse shutdownManagers()
			throws RestClientException {
		final String shutdownManagersUrl = getFormattedUrl(
				shutdownManagersControllerUrl,
				SHUTDOWN_MANAGERS_URL_FORMAT);
		log(Level.FINE, "[shutdownManagers] - sending DELETE request to REST ["
				+ shutdownManagersUrl + "]");
		return executor.delete(
				shutdownManagersUrl,
				new TypeReference<Response<ShutdownManagementResponse>>() {
				});
	}

	/**
	 * 
	 * @param fileSizeLimit 
	 * @return {@link GetPUDumpFileResponse} containing the dump of all the processing units.
	 * @throws RestClientException 
	 */
	public GetPUDumpFileResponse getPUDumpFile(final long fileSizeLimit)
			throws RestClientException {
		long actualFileSizeLimit = fileSizeLimit;
		if (fileSizeLimit == 0) {
			actualFileSizeLimit = CloudifyConstants.DEFAULT_DUMP_FILE_SIZE_LIMIT;
		}
		final String getPUDumpFileUrl = getFormattedUrl(
				shutdownManagersControllerUrl,
				GET_PU_DUMP_FILE_URL_FORMAT,
				Long.toString(actualFileSizeLimit));
		log(Level.FINE, "[getPUDumpFile] - sending GET request to REST ["
				+ getPUDumpFileUrl + "]");
		return executor.get(
				getPUDumpFileUrl,
				new TypeReference<Response<GetPUDumpFileResponse>>() {
				});
	}

	/**
	 * @param ip
	 *            The machine IP.
	 * @param processors
	 *            The list of processors to be used.
	 * @param fileSizeLimit
	 *            The dump file size limit.
	 * @return {@link GetMachineDumpFileResponse} containing the dump data of the machine.
	 * @throws RestClientException 
	 */
	public GetMachineDumpFileResponse getMachineDumpFile(
			final String ip, final long fileSizeLimit, final String processors)
			throws RestClientException {
		if (ip == null) {
			throw MessagesUtils.createRestClientException(CloudifyErrorMessages.MACHINE_IP_MISSING.getName());
		}
		
		long actualSizeLimit = fileSizeLimit;
		if (fileSizeLimit == 0) {
			actualSizeLimit = CloudifyConstants.DEFAULT_DUMP_FILE_SIZE_LIMIT;
		}
				
		String actualProcessors = processors;
		if (StringUtils.isBlank(processors)) {
			actualProcessors = ProcessorTypes.DEFAULT_PROCESSORS;
		}
		
		final String getMachineDumpFileURL = getFormattedUrl(
				shutdownManagersControllerUrl,
				GET_MACHINE_DUMP_FILE_URL_FORMAT,
				ip,
				actualProcessors,
				Long.toString(actualSizeLimit));
		
		log(Level.FINE, "[getPUDumpFile] - sending GET request to REST ["
				+ getMachineDumpFileURL + "]");
		
		return executor.get(
				getMachineDumpFileURL,
				new TypeReference<Response<GetMachineDumpFileResponse>>() {
				});
	}
	
	/**
	 * @param processors
	 *            The list of processors to be used.
	 * @param fileSizeLimit
	 *            The dump file size limit.
	 * @return {@link GetMachinesDumpFileResponse} containing the dump data of all the machines.
	 * @throws RestClientException 
	 */
	public GetMachinesDumpFileResponse getMachinesDumpFile(final String processors, final long fileSizeLimit)
			throws RestClientException {
		
		long actualSizeLimit = fileSizeLimit;
		if (fileSizeLimit == 0) {
			actualSizeLimit = CloudifyConstants.DEFAULT_DUMP_FILE_SIZE_LIMIT;
		}
		
		String actualProcessors = processors;
		if (StringUtils.isBlank(processors)) {
			actualProcessors = ProcessorTypes.DEFAULT_PROCESSORS;
		}
		
		final String getMachinesDumpFileURL = getFormattedUrl(
				shutdownManagersControllerUrl,
				GET_MACHINES_DUMP_FILE_URL_FORMAT,
				actualProcessors,
				Long.toString(actualSizeLimit));
		
		log(Level.FINE, "[getPUDumpFile] - sending GET request to REST ["
				+ getMachinesDumpFileURL + "]");
		
		return executor.get(
				getMachinesDumpFileURL,
				new TypeReference<Response<GetMachinesDumpFileResponse>>() {
				});
	}
	
	private void log(final Level level, final String msg) {
		if (logger.isLoggable(level)) {
			logger.log(level, msg);
		}
	}
	
	/*********
	 * Resolves the configured rest client socket timeout or the default
	 * @return the the configured rest client socket timeout or the default
	 */
	public int getSocketTimeout() {
		
		int restClientSocketTimeout;
		final String restClientSocketTimeoutSysProp = 
				System.getProperty(CloudifyConstants.SYSTEM_PROPERTY_REST_CLIENT_SOCKET_TIMEOUT_MILLIS);
		
		if (StringUtils.isNotBlank(restClientSocketTimeoutSysProp)) {
			try {
				restClientSocketTimeout = Integer.parseInt(restClientSocketTimeoutSysProp);
			} catch (final NumberFormatException nfe) {
				restClientSocketTimeout = CloudifyConstants.DEFAULT_HTTP_READ_TIMEOUT;
				logger.severe("Failed to parse integer system property: "
						+ CloudifyConstants.SYSTEM_PROPERTY_REST_CLIENT_SOCKET_TIMEOUT_MILLIS + ". Value was: " 
						+ restClientSocketTimeoutSysProp + ". Using default value "
						+ CloudifyConstants.DEFAULT_HTTP_READ_TIMEOUT + " instead");
			}
		} else {
			// use the default socket timeout
			restClientSocketTimeout = CloudifyConstants.DEFAULT_HTTP_READ_TIMEOUT;
		}
		
		return restClientSocketTimeout;
	}

}